import os
import glob
import sys
import copy
import re
from typing import Union, List
from ExcelAPI import *
from SVDClasses import *

SIGLEN  = 20
RTL_REG_RREFIX = "ff_"
RTL_WIR_RREFIX = "wir_"
RTL_BITFIELD_EMPTY  = "      "

"""
    svd_info["device"] = info_of_device
    svd_info["cpu"] = info_of_cpu
    svd_info["peripherals_dic"] = {}
    svd_info["peripherals_dic"]["overview"] = peripherals_overview_infos
    svd_info["peripherals_dic"]["alias"] = peripheral_alias
    svd_info["peripherals_dic"]["headerStructNameGroup"] = peripheral_headerStructNameGroup    
    svd_info["peripherals"][peripheral] = info_of_one_peripheral
"""

"""
    dict key 
    rtl_peri["name"]        
    rtl_peri["headname"]    
    rtl_peri["addr"]         
    rtl_peri["bus"]           
    rtl_peri["reg"]          

    rtl_reg["name"]         
    rtl_reg["offset"]        
    rtl_reg["field"]            

    rtl_filed["name"]       
    rtl_filed["access"]     
    rtl_filed["msb"] 
    rtl_filed["lsb"] 
    rtl_filed["resetValue"] 
"""

FILE_HEAD_START = """// +FHDR----------------------------------------------------------
// Copyright (c) 2023, RJMicro Technology Co.,Ltd.
// RJMicro Confidential Proprietary
// ---------------------------------------------------------------
// FILE NAME       : .v
// DEPARTMENT      : IC Dept
// AUTHOR          :
// AUTHOR'S EMAIL  :
// ---------------------------------------------------------------
//
// Description     :
"""

FILE_HEAD_END = """// -FHDR
// ---------------------------------------------------------------

"""

APB_SIGNALS = """    input                  pclk                ,
    input                  prstn               ,

    input                  psel                ,
    input  [11:0]          paddr               ,
    input                  penable             ,
    input                  pwrite              ,
    input  [31:0]          pwdata              ,
//    output                pready              ,
//    output                pslverr             ,
    output [31:0]          prdata
);
"""

APB_WREN = """
// ------------------------------------------------------------
// APB write read enable
// ------------------------------------------------------------
reg     [31:0]  ff_rdata;
wire            read_en   = psel && (~penable) && (~pwrite);
wire            write_en  = psel && (~penable) && pwrite;
wire    [11:0]  addr      = paddr;
wire    [31:0]  wdata     = pwdata;

always @(posedge pclk or negedge prst) begin
    if (!prst)
        prdata <= 32'b0;
    else if (read_en) 
        prdata <= ff_rdata[31:0];
end
"""

AHB_WREN = """
// ------------------------------------------------------------
// AHB write read enable
// ------------------------------------------------------------
wire            ahb_cs    = hsel_{peri} & hready & htrans[1];
wire            read_en   = ahb_cs & (~hwrite);
reg     [11:2]  addr;
reg             write_en;
reg     [31:0]  ff_rdata;

always @(posedge hclk or negedge hrstn) begin
    if (~hrstn) begin
        addr      <= 6'h0;
        write_en  <= 1'h0;
    end else begin
        addr      <= haddr[11:2];
        write_en  <= ahb_cs & (~hwrite);
    end
end

always @(posedge hclk or negedge hrstn) begin
    if (!hrstn)
        hrdata_{peri} <= 32'b0;
    else if (read_en) 
        hrdata_{peri} <= ff_rdata[31:0];
end
"""

AHB_SIGNALS = """    input                  hclk                ,
    input                  hrstn               ,

    input                  hready              ,
    input  [31:0]          haddr               ,
    input                  hwrite              ,
    input  [01:0]          htrans              ,
    input  [02:0]          hsize               ,
    input  [31:0]          hwdataa              ,

//   output                 hreadyout           ,
//   output                 hreadyout_peri      ,
"""

INTERNAL_SIGNALS = """
// ------------------------------------------------------------
// Internal Signals
// ------------------------------------------------------------
"""

WRITE_PROCESS = """
// ------------------------------------------------------------
// write_process
// ------------------------------------------------------------
"""


READ_PROCESS = """
// ------------------------------------------------------------
// read_process
// ------------------------------------------------------------
"""

READ_ADDR_NAME_BEGIN = """always @ (*) begin
    ff_rdata = 32'h0;
    if (read_en) begin
        case (addr[11:2])
"""

READ_ADDR_NAME_END = """            default: ff_rdata = 32'h0;
        endcase
    end
end
"""

MOUDLE_END = """// ------------------------------------------------------------
// End of the module
// ------------------------------------------------------------
endmodule
"""

ASSIGN_NOTE = """// ------------------------------------------------------------
// Assign
// ------------------------------------------------------------
"""



def get_reg_addr(pos_str :str):
    hexval = hex(int(pos_str, 16) >> 2)
    if hexval == '0x0':
        rawstr = "0"
    else:
        rawstr = str(hexval).lstrip("0x")

    return  "10" + "'h" +  rawstr

def get_field_resetval(reg_resetval: str, msb :int, lsb: int):
    bitlen = msb - lsb + 1
    rawval = (int(reg_resetval, 16) >> lsb) & ((1 << (msb - lsb + 1)) - 1)
    hexval = hex(rawval)
    if hexval == '0x0':
        rawstr = "0"
    else:
        rawstr = str(hexval).lstrip("0x")
  
    return str(bitlen) + "'h" +  rawstr

def get_name_str(prefix: str, name: str, str_len: int):
    """
    该函数根据给定的前缀、姓名和期望的字符串长度，生成一个格式化后的姓名字符串。
    如果姓名的长度小于期望的字符串长度，则在姓名后追加空格，直到达到期望的长度；
    如果姓名的长度大于等于期望的字符串长度，则不进行任何处理。
    """
    name_out = ""
    name_len = len(prefix + name)
    if name_len < str_len:
        name_out = prefix + name + " " * (str_len - name_len)
    else:
        name_out = prefix + name

    return name_out

def get_filed_len_str(msb: int, lsb: int):
    """
    该函数根据提供的最高有效位msb和最低有效位lsb计算字段长度,
    并返回一个格式化的字符串表示该长度。如果msb和lsb之间的差值大于0,
    则格式化的字符串表示这个差值;如果差值为0,则返回空字符串。
    """
    str_out = ""
    if msb - lsb != 0:
        str_len = "%02d" % (msb - lsb)
        str_out = f"[{str_len}:{'0'}]"
    return str_out

def get_head_reg_offset_str(offset: str, name: str):
    reg_note_str = ""
    addr_offset = "12'h" + "{:#05X}".format(int(offset, 16))[2:]
    reg_note_str = "// " + addr_offset + "  " + str.upper(name) + "\n"

    return reg_note_str

def get_rdata_str(offset: str, name: str):
    read_str = ""
    rawstr = ""
    binval = bin(int(offset, 16) >> 2)[2:].rjust(10, '0')
    if binval == 0:
        rawstr = "0000000000"
    else:
        rawstr = str(binval)

    read_str = " " * 12 + "10'b" + rawstr + "     :    " + "ff_rdata = " + RTL_WIR_RREFIX + "r_" + name + ";\n"

    return read_str

def get_reg_data_combined(reg_bit_filed_list: list):
    bitarray = ['0'] * 32
    all_field_list = reg_bit_filed_list.copy()
    pos = 0
    for item in reg_bit_filed_list:
        for pos in range(item['lsb'], item['msb'] + 1):
            bitarray[pos] = '1'
    
    str_cover = ''.join(bitarray)
    pos_offset = 0
    while True:
        ret = re.search('0+',str_cover) 
        if ret:
            all_field_list.append({'lsb': ret.span()[0] + pos_offset, 'msb': ret.span()[1] + pos_offset - 1, \
                                   'name': "Reserve"})
            str_cover = str_cover[ret.span()[1]:]
            pos_offset = ret.span()[1] + pos_offset
        else:
            break

    sorted_field_list = sorted(all_field_list, key=lambda x:x["lsb"], reverse=True) 

    outstr = "{"
    for item in sorted_field_list:
        name = ""
        if item["name"] == "Reserve":
            name = str(item["msb"] + 1 - item["lsb"]) + "'h0"
        else:
            if item["msb"] == item["lsb"]:
                name = item["name"]
            else:
                name = item["name"] + "[" + str(item["msb"]) + ":" + str(item["lsb"]) + "]"

        outstr += name + ", "
    
    outstr = outstr.rstrip(", ") + "};\n"
    return outstr

def get_wfield_clk_str(bus :str, wren: str, wfieldname: str, resetValue: str, msb :int, lsb: int):
    seqstr = ""
    busclk = ""
    busrst = ""
    bitrange = ""
    if msb == lsb:
        bitrange = lsb
    else:
        bitrange = str(msb) + ":" + str(lsb)

    if str.lower(bus) == "apb":
        busclk = "pclk"
        busrst = "prstn"
    elif str.lower(bus) == "ahb":
        busclk = "hclk"
        busrst = "hrstn"
    
    seqstr = "always @(posedge {busclk} or negedge {busrst}) begin\n".format(busclk=busclk, busrst=busrst) + \
            "    if (!{busrst})\n".format(busrst=busrst)  + \
            "        {wfieldname} <= {resetValue};\n".format( wfieldname=wfieldname, resetValue=resetValue) + \
            "    else if ({wren}) begin\n".format(wren = wren) + \
            "        {wfieldname} <= wdata[{bitrange}];\n".format(wfieldname=wfieldname, bitrange = bitrange) + \
            "    end\n" + \
            "end\n\n"

    return  seqstr


def get_wfield_wo_clk_str(bus :str, wren: str, wfieldname: str, resetValue: str, msb :int, lsb: int):
    seqstr = ""
    busclk = ""
    busrst = ""
    bitrange = ""
    if msb == lsb:
        bitrange = lsb
    else:
        bitrange = str(msb) + ":" + str(lsb)
    reglen = msb - lsb + 1

    if str.lower(bus) == "apb":
        busclk = "pclk"
        busrst = "prstn"
    elif str.lower(bus) == "ahb":
        busclk = "hclk"
        busrst = "hrstn"
    
    seqstr = "always @(posedge {busclk} or negedge {busrst}) begin\n".format(busclk=busclk, busrst=busrst) + \
            "    if (!{busrst})\n".format(busrst=busrst)  + \
            "        {wfieldname} <= {resetValue};\n".format( wfieldname=wfieldname, resetValue=resetValue) + \
            "    else if ({wren})\n".format(wren = wren) + \
            "        {wfieldname} <= wdata[{bitrange}];\n".format(wfieldname=wfieldname, bitrange = bitrange) + \
            "    else \n" + \
            "        {wfieldname} <= {reglen}'h0;\n".format(wfieldname=wfieldname, reglen = reglen) + \
            "end\n\n"

    return  seqstr

    
def gen_rtlreg_string_from_info(svd_info :dict,outputDir = "./"):
    rtl_peri = get_rtlreg_string_from_info(svd_info)
    gen_rtlreg_file(rtl_peri,outputDir)

def get_rtlreg_string_from_info(svd_info :dict):
        
    rtl_peri = []
    rtl_headname = []
    #遍历外设
    for peripheral,peripheral_dic in svd_info["peripherals"].items():
        peri_item = {}
        # 相同头的外设只输出1个RTL文件
        if peripheral_dic["headerStructName"] != None: 
            if peripheral_dic["headerStructName"] not in rtl_headname:
                rtl_headname.append(peripheral_dic["headerStructName"])
                peri_item['name'] = peripheral_dic["headerStructName"]
            else:
                continue
        else:
            if peripheral != None:
                peri_item['name'] = peripheral   
            else:
                continue     
         
        # 不同头的外设只输出不同的RTL文件
        # if peripheral != None:
        #     peri_item['name'] = peripheral   
        # else:
        #     continue      

        # peri_item["headname"] = peripheral_dic['headerStructName']
        peri_item['addr'] = peripheral_dic['baseAddress']
        peri_item['bus']  = peripheral_dic['BUS'] 
        peri_item['reg']  = []
        rtl_peri.append(peri_item)
        
        # #需要判断是不是需要derivedFromStr
        # derivedFromStr = get_peripheral_derivedFromStr(svd_info["peripherals"][peripheral],svd_info["peripherals_dic"]["headerStructNameGroup"])
        # svd_info["peripherals"][peripheral]["derivedFrom"] = derivedFromStr
        
        # #生成这个peripheral的inst
        # peripheral_inst = Peripheral.gen_inst_from_dict(peripheral_dic)        
        # if not peripheral_inst :
        #     print(f"[Error]Fail to gen peripheral inst!!! \n")
        #     return ""
               
            
        # 防止没有 registers        
        # if False and not hasattr(svd_info["peripherals"][peripheral], 'registers'):
        #    continue
            
        #遍历peripheral的regs
        if svd_info["peripherals"][peripheral]["has_registers"] :
            svd_registers = svd_info["peripherals"][peripheral]["registers"]
            if not (svd_registers and len(svd_registers) > 0):
                namestr = peripheral_dic["name"]
                print(f"[Error] peripheral[{namestr}] has no register\n")
                return ""
            
            for register, register_dic in svd_registers.items():
                register_inst = Register.gen_inst_from_dict(register_dic)
                if register_dic["name"] == None:
                    continue

                if not register_inst :                
                    namestr = register_dic["name"]
                    print(f"[Error]Fail to gen register[{namestr}] inst!!! \n")
                    return ""
                
                reg_item = {}
                reg_item['name'] = register_dic["name"]
                reg_item['offset'] = register_dic["addressOffset"]
                reg_item['field'] = []
                rtl_peri[-1]['reg'].append(reg_item)
                #遍历register的fields
                svd_fields = register_dic["fields"]
                if not (svd_fields and len(svd_fields) > 0): 
                    namestr = register_dic["name"]
                    print(f"[Warning] register[{namestr}] has no fields?\n")
                    continue            
                
                for field,field_dic in svd_fields.items():
                    field_item = {}
                    field_item['name'] = field
                    field_item['access'] = field_dic["access"]
                    field_item['msb'] = field_dic["msb"]
                    field_item['lsb'] = field_dic["lsb"]
                    field_item['resetValue'] = get_field_resetval(register_dic["resetValue"], \
                                                field_dic["msb"], field_dic["lsb"])
                    rtl_peri[-1]['reg'][-1]['field'].append(field_item)
    return rtl_peri

def gen_rtlreg_file(rtl_peri :dict,outputDir = "./"):

    for peri_item in rtl_peri:
        peri_name = str.lower(peri_item["name"])

        rtl_str = ""
        rtl_head_offset_name_str = ""
        rtl_module_list_str = "module " + str.lower(peri_item["name"]) +"_regfile (\n" 
        rtl_ff_list_str = ""
        rtl_read_name_list_str = "\n"
        rtl_read_assign_list_str = ""

        rtl_wen_list_str = ""
        rtl_wclk_list_str = "\n"

        rtl_wir_read_reg_list_str = "\n"
        rtl_wir_read_pos_list_str = "\n" + READ_ADDR_NAME_BEGIN
        rtl_ff_output_str = ASSIGN_NOTE

        

        for reg_item in peri_item["reg"] :
            reg_name = str.lower(reg_item["name"]) 
            reg_wen_flag = 0
            reg_ren_flag = 0
            reg_bit_filed_list = []
            rtl_head_offset_name_str += get_head_reg_offset_str(reg_item["offset"], reg_name)

            sorted_field_list = sorted(reg_item["field"], key=lambda x:x["lsb"])
            for field_item in sorted_field_list:
                if reg_name == str.lower(field_item["name"]):
                    field_name = reg_name
                else:
                    field_name = reg_name + "_" + str.lower(field_item["name"])

                reg_bit_filed_item = {}  
                
                reg_bit_filed_item['lsb']  = field_item["lsb"]
                reg_bit_filed_item['msb']  = field_item["msb"]
                if field_item["access"] == "RW":
                    reg_bit_filed_item['name'] =  RTL_REG_RREFIX + field_name
                    reg_bit_filed_list.append(reg_bit_filed_item)
                    reg_wen_flag = 1
                    reg_ren_flag = 1

                    bitlen_str = get_filed_len_str(field_item["msb"], field_item["lsb"])
                    if bitlen_str == "":
                        bitlen_str = RTL_BITFIELD_EMPTY

                    rtl_module_list_str += "    output " + bitlen_str + 10 * ' ' + \
                                    get_name_str("", field_name, SIGLEN) + ",\n"
                    rtl_ff_list_str += "reg     " + bitlen_str + 2 * ' ' + \
                                    get_name_str(RTL_REG_RREFIX, field_name, SIGLEN) + ";\n" 

                    rtl_wclk_list_str += get_wfield_clk_str(peri_item["bus"], "wren_" + reg_name,\
                                        RTL_REG_RREFIX + field_name,\
                                        field_item['resetValue'],  field_item["msb"], field_item["lsb"])
                    rtl_ff_output_str += "assign  " + get_name_str("", field_name, SIGLEN) + "= " + \
                                    get_name_str(RTL_REG_RREFIX, field_name, SIGLEN) + ";\n"

                elif field_item["access"] == "RO":
                    reg_ren_flag = 1
                    reg_bit_filed_item['name'] =  RTL_WIR_RREFIX + field_name
                    reg_bit_filed_list.append(reg_bit_filed_item)
                    bitlen_str = get_filed_len_str(field_item["msb"], field_item["lsb"])
                    if bitlen_str == "":
                        bitlen_str = RTL_BITFIELD_EMPTY

                    rtl_module_list_str += "    input  " + bitlen_str + 10 * ' ' + field_name + \
                                            (SIGLEN - len(field_name)) * ' ' + ",\n"
                    
                    rtl_read_name_list_str += "wire    " + bitlen_str + 2 * ' ' + \
                                get_name_str(RTL_WIR_RREFIX, field_name, SIGLEN)  + ";\n" 

                    rtl_read_assign_list_str += "assign" + 10 * ' ' +get_name_str(RTL_WIR_RREFIX, field_name, SIGLEN)+\
                        "= " + get_name_str("" , field_name + bitlen_str.strip(), SIGLEN) + ";\n"
                        
                elif field_item["access"] == "WO":
                    reg_wen_flag = 1
                    bitlen_str = get_filed_len_str(field_item["msb"], field_item["lsb"])
                    rtl_module_list_str += "    output " + bitlen_str + 10 * ' ' + \
                                    get_name_str("", field_name, SIGLEN) + ",\n"
                    rtl_ff_list_str += "reg     " + bitlen_str + 2 * ' ' + \
                                    get_name_str(RTL_REG_RREFIX, field_name, SIGLEN) + ";\n" 
                    
                    rtl_wclk_list_str += get_wfield_wo_clk_str(peri_item["bus"], "wren_" + reg_name,\
                                        RTL_REG_RREFIX + field_name,\
                                        field_item['resetValue'],  field_item["msb"], field_item["lsb"])

                    rtl_ff_output_str += "assign  " + get_name_str("", field_name, SIGLEN) + "= " + \
                                    get_name_str(RTL_REG_RREFIX, field_name, SIGLEN) + ";\n"

            if reg_wen_flag == 1:
                rtl_wen_list_str += "wire " + 4 * ' ' + get_name_str("wren_", reg_name, SIGLEN) +\
                                "= write_en & (addr[11:2] == "  + get_reg_addr(reg_item["offset"]) + ");\n"

            if reg_ren_flag == 1: 
                rtl_wir_read_reg_list_str += "wire  [31:0]  " + get_name_str(RTL_WIR_RREFIX+"r_", reg_name, \
                                            SIGLEN - 5) + "= " + get_reg_data_combined(reg_bit_filed_list)
                rtl_wir_read_pos_list_str += get_rdata_str(reg_item["offset"], reg_name)

        rtl_str += FILE_HEAD_START + rtl_head_offset_name_str + FILE_HEAD_END  
        rtl_str += rtl_module_list_str
        if peri_item["bus"] == "APB":
            rtl_str += APB_SIGNALS
            rtl_str += APB_WREN
        elif peri_item["bus"]  == "AHB":
            rtl_str += AHB_SIGNALS
            rtl_str += "    input                   hsel_" + str.lower(peri_item["name"] ) + \
                        (SIGLEN - 5 - len(peri_item["name"] )) * ' ' + ",\n"
            rtl_str += "    output [31:0]           hrdata_" + str.lower(peri_item["name"] ) + "\n"
            rtl_str += ");\n"
            rtl_str += AHB_WREN.format(peri = peri_name)


        rtl_str += INTERNAL_SIGNALS + rtl_ff_list_str + rtl_read_name_list_str + rtl_read_assign_list_str +\
                  WRITE_PROCESS + rtl_wen_list_str + rtl_wclk_list_str + READ_PROCESS + rtl_wir_read_reg_list_str +\
                  rtl_wir_read_pos_list_str + READ_ADDR_NAME_END + rtl_ff_output_str + MOUDLE_END
            
        outputfile = os.path.join(outputDir,peri_item["name"]  + "_regfile.v")
        save_to_file(outputfile, rtl_str)  

def save_to_file(file_path: str, strToSave: str):
    with open(file_path, mode='w', encoding='utf-8') as file:
        file.write(strToSave)
        print(f"{file_path} saved successfully.\n");